/*
 * OtherFsOperations.cpp
 *
 *  Created on: Apr 8, 2010
 *      Author: Lukasz Spintzyk
 *
 */

#include "ph.h"
#include "FileOperations.h"
#include "OtherFsOperations.h"
#include "DirOperations.h"
#include "ConfigOperations.h"
#include "Filesystem.h"
#include "InodeReader.h"
#include "DirectoryReader.h"
#include "FSException.h"
#include "fusecpp.h"
#include "utils.h"
#include "version.h"

namespace CudaCryptoFs {

fuse_cpp::FuseDispatcher& initDispatcher()
{
	static fuse_cpp::FuseDispatcher fuseDis;

	fuseDis.set_access(CudaCryptoFs::access);
	fuseDis.set_open(CudaCryptoFs::open);
	fuseDis.set_getattr(CudaCryptoFs::getattr);
	fuseDis.set_read(CudaCryptoFs::read);
	fuseDis.set_readdir(CudaCryptoFs::readdir);
	//fuseDis.set_getdir(CudaCryptoFs::getdir);


	fuseDis.set_init(CudaCryptoFs::init);
	fuseDis.set_destroy(CudaCryptoFs::destroy);
	fuseDis.set_mkdir(CudaCryptoFs::mkdir);
	fuseDis.set_rmdir(CudaCryptoFs::rmdir);
	fuseDis.set_rename(CudaCryptoFs::rename);
	fuseDis.set_opendir(CudaCryptoFs::opendir);
	fuseDis.set_chmod(CudaCryptoFs::chmod);
	fuseDis.set_chown(CudaCryptoFs::chown);
	fuseDis.set_releasedir(CudaCryptoFs::releasedir);

	fuseDis.set_utimens(CudaCryptoFs::utimens);

	fuseDis.set_statfs(CudaCryptoFs::statfs);
	fuseDis.set_readlink(CudaCryptoFs::readlink);
	fuseDis.set_symlink(CudaCryptoFs::symlink);
	fuseDis.set_link(CudaCryptoFs::link);


	// File operations
	fuseDis.set_create(CudaCryptoFs::create);
	fuseDis.set_fgetattr(CudaCryptoFs::fgetattr);
	fuseDis.set_open(CudaCryptoFs::open);
	fuseDis.set_read(CudaCryptoFs::read);
	fuseDis.set_write(CudaCryptoFs::write);
	fuseDis.set_unlink(CudaCryptoFs::unlink);
	fuseDis.set_release(CudaCryptoFs::release);

	fuseDis.set_flush(CudaCryptoFs::flush);

	return fuseDis;
}

/*****************************************************************************/
/*****************************************************************************/

Filesystem* getFS()
{
	struct fuse_context* context = fuse_get_context();
	Filesystem * fs = (Filesystem*)context->private_data;
	return fs;
}

/*****************************************************************************/
/*****************************************************************************/

void *init(struct fuse_conn_info *conn) {
	struct fuse_context* context = fuse_get_context();
	struct FilesystemMountArgs* args =
			(struct FilesystemMountArgs*) context->private_data;
	std::cout << "FS start" << std::endl;
	args->filesystem = Filesystem::read(*args);
	Filesystem * fs = args->filesystem.get();
	return fs;
}

/*****************************************************************************/
/*****************************************************************************/

void destroy(void * fs) {
	std::cout << "FS close " << std::endl;
	// TODO do any necessary filesystem closing
}

/*****************************************************************************/
/*****************************************************************************/

int statfs(const char *path, struct statvfs *stat)
{
	// TODO write an plug
	// TODO refink that values
	Filesystem* fs = getFS();
	SuperBlock& sb = fs->getSuperBlock();
	// TODO stat->f_fsid = ??
	stat->f_fsid = MAGIC_NUMBER;
	stat->f_bsize = sb.d_blockSize;
	stat->f_blocks = sb.d_blocksCount;
	stat->f_bfree = sb.d_freeBlocks;
	stat->f_bavail = sb.d_freeBlocks;
	stat->f_files = sb.d_inodesCount;
	stat->f_frsize = sb.d_freeInodes;
	stat->f_namemax = sizeof(byte)*8;

	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int readlink(const char *file, char *outbuf, size_t size)
{
	// TODO write an plug
	Filesystem* fs = getFS();
	DirectoryReader* dr = fs->getDirectoryReader();
	BlockManager* bMgr = fs->getBlockReader();
	try
	{
		boost::shared_ptr<Inode> link = dr->path2Inode(file);
		if (link->d_blocks == 0)
		{
			strcpy(outbuf,(char*) link->d_block);
		}
		else
		{
			bMgr->getBlock(link->d_block[0], outbuf); // TODO change getBlock method
		}

	}
	catch (FSException& e)
	{
		return -ENOENT;
	}
	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int mknod(const char *, mode_t, dev_t)
{
	// TODO write an plug
		return -ENOENT;
}

/*****************************************************************************/
/*****************************************************************************/

int link(const char *file, const char *link)
{
	// TODO write an plug
	std::cout << "FileH " << file << std::endl;
	std::cout << "linkH " << link << std::endl;
	try
	{
		Filesystem* fs = getFS();
		DirectoryReader* dr = fs->getDirectoryReader();
		InodePtr fileInode = dr->path2Inode(file);

		InodePtr dir = dr->path2Inode(getParentDir(link));
		assert(!dir->isDir());
		std::string fileName = findLastName(link);

		fileInode->d_linksCount++;
		DirEntry file_entry(fileName, fileInode->d_num, S_IFREG);
		dr->addDirEntry(file_entry, dir);
		fs->getInodeReader()->setInode(fileInode->d_num, fileInode);
	}
	catch(FSException& e)
	{
		return -e.code;
	}
	return 0;
}

/*****************************************************************************/
/*****************************************************************************/

int symlink(const char *file, const char *link)
{
	try
	{
		// TODO write an plug
		//TODO check fest long links!!!!!!!!!!!!
		Filesystem* fs = getFS();
		InodeManager* ir = fs->getInodeReader();

		boost::shared_ptr<Inode> inode = ir->createNewInode();
		inode->initLinkInode(fuse_get_context());
		set_link_inode_content(file, inode);
		add_link_to_parent_directory(link, inode);
		return 0;
	}
	catch (FSException& e)
	{
		return -ENOENT;
	}
}

void set_link_inode_content(const char* file, InodePtr inode)
{
	if (strlen(file)+1 < sizeof(inode->d_block))
	{
		memcpy(inode->d_block, file, strlen(file)+1);
		inode->d_size = strlen(file);
	}
	else
	{
		InodeManager* ir = getFS()->getInodeReader();
		ir->reserveBlocksForInode(inode,1);
		BlockManager* bmgr = getFS()->getBlockReader();
		bmgr->writeStringToBlock(inode->d_block[0], file);
		inode->d_size = getFS()->getSuperBlock().d_blockSize;
	}
}

void add_link_to_parent_directory(const char* link, InodePtr inode)
{
	std::string parent_dir = getParentDir(link);

	DirectoryReader* dr = getFS()->getDirectoryReader();

	boost::shared_ptr<Inode> parent = dr->path2Inode(parent_dir);
	DirEntry entry(findLastName(link), inode->d_num, EXT2_FT_SYMLINK);
	dr->addDirEntry(entry, parent);
}


/*****************************************************************************/
/*****************************************************************************/

int access(const char *path, int)
{
	// TODO write an plug
	try
	{
		Filesystem* fs = getFS();
		DirectoryReader* dr = fs->getDirectoryReader();
		boost::shared_ptr<Inode> inode = dr->path2Inode(path);

		struct fuse_context* context = fuse_get_context();
		uint16_t uid = context->uid;
		uint16_t gid = context->gid;

		if (inode->isDir() && execute_ok(inode, uid, gid))
		{
			return 0;
		}
		else if (inode->isFile() && read_ok(inode, uid, gid))
		{
			return 0;
		}

		return -EACCES;
	}
	catch(FSException& e)
	{
		return -e.code;
	}
	return 0;

}

} //end namespace CudaCryptoFs
